package nc.ui.pub.filesystem.treetable;

import java.awt.Color;
import java.awt.Component;
import java.awt.Graphics;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.KeyEvent;
import java.awt.event.MouseEvent;
import java.lang.reflect.Method;
import java.security.AccessController;
import java.security.PrivilegedAction;

import javax.swing.JComponent;
import javax.swing.JPanel;
import javax.swing.JTable;
import javax.swing.JTree;
import javax.swing.KeyStroke;
import javax.swing.ListSelectionModel;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.border.Border;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.event.TreeExpansionEvent;
import javax.swing.event.TreeExpansionListener;
import javax.swing.event.TreeModelEvent;
import javax.swing.event.TreeModelListener;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.TableCellEditor;
import javax.swing.table.TableCellRenderer;
import javax.swing.table.TableModel;
import javax.swing.tree.DefaultTreeCellRenderer;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.DefaultTreeSelectionModel;
import javax.swing.tree.MutableTreeNode;
import javax.swing.tree.TreeCellRenderer;
import javax.swing.tree.TreeModel;
import javax.swing.tree.TreePath;

public class CTreeTable extends JTable {
	private static final long serialVersionUID = -1765638469237277178L;

	private class CTree extends JTree implements TableCellRenderer {
		private static final long serialVersionUID = -1476989213412274165L;
		protected Border highlightBorder = null;
		private int rowToPaint = -1;
		private int colToPaint = -1;

		
		public void paint(Graphics g) {
			int row = rowToPaint;
			int col = colToPaint;
			if (row > -1 && col > -1) {
				g.translate(0, -row * getRowHeight());

			}
			super.paint(g);
			if (highlightBorder != null) {
				highlightBorder.paintBorder(this, g, 0, row * getRowHeight(), getWidth(), getRowHeight() - 1);
			}
		}

		//
		public void setBounds(int x, int y, int w, int h) {
			super.setBounds(x, 0, w, CTreeTable.this.getHeight());
		}

		
		public void processMouseEvent(MouseEvent e) {
			super.processMouseEvent(e);
		}

		
		public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
			this.rowToPaint = row;
			this.colToPaint = column;
			Color background = null;
			Color foreground = null;

			if (isSelected) {
				background = table.getSelectionBackground();
				foreground = table.getSelectionForeground();
			} else {
				background = table.getBackground();
				foreground = table.getForeground();
			}
			int editingRow = table.getEditingRow();
			// if (editingRow != -1 && editingRow == rowToPaint &&
			// table.getEditingColumn() == column) {
			if (editingRow != -1 && editingRow == row && table.getEditingColumn() == column) {

				background = UIManager.getColor("Table.focusCellBackground");
				foreground = UIManager.getColor("Table.focusCellForeground");
			}
			highlightBorder = null;
			if (hasFocus) {
				highlightBorder = UIManager.getBorder("Table.focusCellHighlightBorder");
			}
			this.setBackground(background);

			TreeCellRenderer tcr = this.getCellRenderer();
			if (tcr instanceof DefaultTreeCellRenderer) {
				DefaultTreeCellRenderer dtcr = ((DefaultTreeCellRenderer) tcr);

				if (this.getSelectionModel().isRowSelected(rowToPaint)) {
					dtcr.setTextSelectionColor(foreground);
					dtcr.setBackgroundSelectionColor(background);
				} else {
					dtcr.setTextNonSelectionColor(foreground);
					dtcr.setBackgroundNonSelectionColor(background);
				}
			}

			return this;
		}
	}

	private class SelectionModelAdapter extends DefaultTreeSelectionModel implements ListSelectionListener {
		private static final long serialVersionUID = 4103331087476323907L;
		protected boolean updatingListSelectionModel;

		public SelectionModelAdapter() {
			super();
			getListSelectionModel().addListSelectionListener(this);
		}

		public ListSelectionModel getListSelectionModel() {
			return super.listSelectionModel;
		}

		
		public void resetRowSelection() {
			if (!updatingListSelectionModel) {
				try {
					updatingListSelectionModel = true;
					super.resetRowSelection();
				} finally {
					updatingListSelectionModel = false;
				}
			}

		}

		public void valueChanged(ListSelectionEvent e) {
			if (!updatingListSelectionModel) {
				try {
					updatingListSelectionModel = true;
					int min = listSelectionModel.getMinSelectionIndex();
					int max = listSelectionModel.getMaxSelectionIndex();
					clearSelection();
					for (int i = min; i <= max; i++) {
						if (listSelectionModel.isSelectedIndex(i)) {
							int row = i;
							TreePath path = tree.getPathForRow(row);
							if (path != null) {
								addSelectionPath(path);
							}
						}
					}
				} finally {
					updatingListSelectionModel = false;
				}
			}
		}

	}

	private class TableModelAdapter extends AbstractTableModel implements TreeExpansionListener, TreeModelListener {
		private static final long serialVersionUID = -5575700666383349689L;

		
		public int getColumnCount() {
			return treeTableModel.getColumnCount();
		}

		
		public int getRowCount() {
			return tree.getRowCount();
		}

		private Object getNode(int row) {
			TreePath path = tree.getPathForRow(row);
			if (path != null) {
				return path.getLastPathComponent();
			}
			return null;
		}

		
		public Object getValueAt(int rowIndex, int columnIndex) {
			Object node = getNode(rowIndex);
			if (node == null)
				return null;
			else
				return treeTableModel.getValueAt(node, rowIndex, columnIndex);

		}

		
		public Class<?> getColumnClass(int columnIndex) {
			return treeTableModel.getColumnClass(columnIndex);
		}

		
		public String getColumnName(int column) {
			return treeTableModel.getColumnName(column);
		}

		
		public boolean isCellEditable(int rowIndex, int columnIndex) {
			Object node = getNode(rowIndex);
			if (node == null)
				return false;
			else
				return treeTableModel.isCellEditable(node, rowIndex, columnIndex);
		}

		
		public void setValueAt(Object value, int rowIndex, int columnIndex) {
			Object node = getNode(rowIndex);
			if (node != null)
				treeTableModel.setValueAt(value, node, rowIndex, columnIndex);
			TreePath path = tree.getPathForRow(rowIndex);
			if (path != null) {
				tree.getModel().valueForPathChanged(path, getValueAt(rowIndex, treeRenderCol));
			}

		}

		// //////////////////////////////
		
		public void treeCollapsed(TreeExpansionEvent event) {
			TreePath path = event.getPath();
			int row = tree.getRowForPath(path);
			if (row > -1 && row < tree.getRowCount()) {
				Object node = path.getLastPathComponent();
				int count = treeTableModel.getChildCount(node);
				if (count > 0)
					fireTableRowsDeleted(row + 1, row + count);
			}

		}

		
		public void treeExpanded(TreeExpansionEvent event) {
			TreePath path = event.getPath();
			int row = tree.getRowForPath(path);
			if (row > -1 && row < tree.getRowCount()) {
				Object node = path.getLastPathComponent();
				int count = treeTableModel.getChildCount(node);
				if (count > 0)
					fireTableRowsInserted(row + 1, row + count);
			}

		}

		// ///////////////////////
		private void fireTreeTableDataChange() {
			Runnable run = new Runnable() {
				
				public void run() {
					fireTableDataChanged();
				}
			};
			SwingUtilities.invokeLater(run);
		}

		
		public void treeNodesChanged(TreeModelEvent e) {
			Runnable run = new Runnable() {
				
				public void run() {
					fireTableRowsUpdated(0, tree.getRowCount() - 1);
				}
			};
			SwingUtilities.invokeLater(run);
		}

		
		public void treeNodesInserted(final TreeModelEvent e) {
			Runnable run = new Runnable() {
				
				public void run() {
					TreePath path = e.getTreePath();
					tree.expandPath(path);
					int[] cis = e.getChildIndices();
					int max = 0;
					int min = Integer.MAX_VALUE;
					int count = cis == null ? 0 : cis.length;
					for (int i = 0; i < count; i++) {
						if (max < cis[i] + 1) {
							max = cis[i] + 1;
						}
						if (min > cis[i] + 1) {
							min = cis[i] + 1;
						}
					}

					int row = tree.getRowForPath(path);
					fireTableRowsUpdated(row + min, row + max);
					fireTableRowsInserted(row + min, row + max);
				}
			};
			SwingUtilities.invokeLater(run);

		}

		
		public void treeNodesRemoved(final TreeModelEvent e) {
			Runnable run = new Runnable() {
				
				public void run() {
					TreePath path = e.getTreePath();
					int[] cis = e.getChildIndices();
					int max = 0;
					int min = Integer.MAX_VALUE;
					int count = cis == null ? 0 : cis.length;
					for (int i = 0; i < count; i++) {
						if (max < cis[i] + 1) {
							max = cis[i] + 1;
						}
						if (min > cis[i] + 1) {
							min = cis[i] + 1;
						}
					}
					int row = tree.getRowForPath(path);
					fireTableRowsUpdated(row + min, row + max);
					fireTableRowsDeleted(row + min, row + max);

				}
			};
			SwingUtilities.invokeLater(run);

		}

		
		public void treeStructureChanged(TreeModelEvent e) {
			fireTreeTableDataChange();

		}

	}


	private CTree tree = null;
	private ITreeTableModel treeTableModel = null;
	private int treeRenderCol = 0;
//	private CTreeTableRowSorter rowSorter = null;;
	public CTreeTable(ITreeTableModel ttm) {
		super();
		this.treeTableModel = ttm;
		intialize();
	}

	private void intialize() {
		tree = new CTree();
		tree.setModel(treeTableModel);
		TableModelAdapter tma = new TableModelAdapter();
		setModel(tma);
		tree.setShowsRootHandles(true);
		tree.setRowHeight(getRowHeight());
		tree.addTreeExpansionListener(tma);
		tree.getModel().addTreeModelListener(tma);
		getColumnModel().getColumn(treeRenderCol).setCellRenderer(tree);
		// /
		SelectionModelAdapter selModel = new SelectionModelAdapter();
		tree.setSelectionModel(selModel);
		setSelectionModel(selModel.getListSelectionModel());
		//
//		rowSorter = new CTreeTableRowSorter();
	}
	
	
	final public void setModel(TableModel dataModel) {
		if (dataModel instanceof TableModelAdapter)
			super.setModel(dataModel);
	}

	
	public final void setRowHeight(int row, int rowHeight) {
		setRowHeight(rowHeight);
	}

	
	public final void setRowHeight(int rowHeight) {
		super.setRowHeight(rowHeight);
		if (tree != null)
			tree.setRowHeight(rowHeight);
	}

	public void setRootVisible(boolean visible) {
		if (tree != null) {
			tree.setRootVisible(visible);
		}
	}

	
	protected final void processMouseEvent(MouseEvent e) {
		Point p = e.getPoint();
		int col = columnAtPoint(p);

		if (col == convertColumnIndexToView(treeRenderCol)) {
			int row = rowAtPoint(p);
			if (row != -1) {
				Rectangle rect1 = getCellRect(row, col, true);
//				row = convertRowIndexToModel(row);
				Rectangle rect2 = getCellRect(row, col, true);
				MouseEvent me = new MouseEvent(tree, e.getID(), e.getWhen(), e.getModifiers(), p.x - rect1.x, p.y - (rect1.y - rect2.y), e.getClickCount(), e.isPopupTrigger(), e.getButton());
				tree.processMouseEvent(me);
			}
		}
		super.processMouseEvent(e);
	}

	
	public Component prepareEditor(TableCellEditor editor, int row, int column) {
		final Component comp = super.prepareEditor(editor, row, column);
		if (column == convertColumnIndexToView(treeRenderCol) && comp != null) {
			final int r = row, col = column;

			final Rectangle rect = getCellRect(r, column, false);
			JPanel panel = new JPanel() {
				private static final long serialVersionUID = -4503378912490172443L;

				
				protected void paintComponent(Graphics g) {
					super.paintComponent(g);
					tree.highlightBorder = null;
					tree.rowToPaint = r;
					tree.colToPaint = col;
					tree.setBounds(rect.x, 0, rect.width, 0);
					Graphics g2 = null;
					try {
						g2 = g.create();
						tree.paint(g2);
					} finally {
						if (g2 != null) {
							g2.dispose();
						}
					}
				}

				protected boolean processCompKeyBinding(final JComponent jcomp, final KeyStroke ks, final KeyEvent e, final int condition, final boolean pressed) {
					boolean retr = AccessController.doPrivileged(new PrivilegedAction<Boolean>() {
						
						public Boolean run() {
							boolean b = false;
							try {
								Method m = JComponent.class.getDeclaredMethod("processKeyBinding", KeyStroke.class, KeyEvent.class, int.class, boolean.class);
								m.setAccessible(true);
								b = (Boolean) m.invoke(jcomp, ks, e, condition, pressed);
							} catch (Exception e1) {
								e1.printStackTrace();
							}
							return b;
						}
					});
					return retr;
				}

				
				protected boolean processKeyBinding(KeyStroke ks, KeyEvent e, int condition, boolean pressed) {
					if (comp instanceof JComponent) {
						boolean b = processCompKeyBinding((JComponent) comp, ks, e, WHEN_FOCUSED, pressed);
						if (getSurrendersFocusOnKeystroke()) {
							comp.requestFocus();
						}
						return b;
					} else {
						return super.processKeyBinding(ks, e, condition, pressed);
					}
				}

			};
			panel.setLayout(null);
			panel.setBounds(rect);
			int x = tree.getRowBounds(r).x;
			TreeCellRenderer tcr = tree.getCellRenderer();
			if (tcr != null && tcr instanceof DefaultTreeCellRenderer) {
				DefaultTreeCellRenderer dtcr = (DefaultTreeCellRenderer) tcr;
				x += dtcr.getIconTextGap() + dtcr.getIcon().getIconWidth();
			}
			comp.setBounds(x, 0, rect.width - x, rect.height);
			panel.add(comp);
			return panel;
		}
		return comp;
	}

	public JTree getTree() {
		return tree;
	}

	public TreePath getSelectionPath() {
		if (tree != null)
			return tree.getSelectionPath();
		else
			return null;
	}

	public TreePath[] getSelectionPaths() {
		if (tree != null) {
			return tree.getSelectionPaths();
		} else {
			return null;
		}

	}

	public void insertNodeIntoParent(MutableTreeNode newChild, MutableTreeNode parent, int index) {
		TreeModel model = tree.getModel();
		if (model instanceof DefaultTreeModel) {
			if (parent == null && treeTableModel != null) {
				Object root = treeTableModel.getRoot();
				if (root instanceof MutableTreeNode) {
					parent = (MutableTreeNode) root;
				}
			}
			DefaultTreeModel treeModel = (DefaultTreeModel) model;
			treeModel.insertNodeInto(newChild, parent, index);
			
		}

	}

	public void removeNodeFromParent(MutableTreeNode node) {
		TreeModel model = tree.getModel();
		if (model instanceof DefaultTreeModel) {
			DefaultTreeModel treeModel = (DefaultTreeModel) model;
			treeModel.removeNodeFromParent(node);
		}
	}
	
}
